import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
import java.nio.ByteBuffer;
import java.sql.Timestamp;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.Calendar;
import java.util.Locale;
import java.util.Timer;
import javax.swing.JLabel;
import main.CEPLFunctions;

public class UDPServer implements Runnable {
	@SuppressWarnings("unused")
	private class DatagramPacketProcessor implements Runnable {
		private byte[] arr;
		private long streamID;

		public DatagramPacketProcessor(byte[] data, int length) {
			ByteBuffer buf = ByteBuffer.wrap(data);
			streamID = buf.getLong();
			arr = new byte[length - 8];
			buf.get(arr);
		}

		public void run() {
			// System.out.println("IN: byte-array for stream " + streamID);
			CEPLFunctions.Events.pushEvent(streamID, arr);
		}
	}

	private static UDPServer sInstance = null;

	public static UDPServer instance() {
		if (sInstance == null)
			sInstance = new UDPServer();

		return sInstance;
	}

	public static final int PORT = 59876;
	private final int BUFFERSIZE = 1024;
	private boolean active = false;
	private DatagramSocket socket = null;
	private Thread serverThread = null;

	// for speed measurement
	private long nbPushesSinceLastUpdate = 0;
	private long nbPushesSinceStart = 0;
	private long totalNbBytes = 0;
	private Timestamp timestampLastUpdate = null;
	private JLabel labelForStats = null;
	private String numberFormatString = null;
	private DecimalFormat numberFormat = null;

	private Timer bytearrayPusherTimer = null;
	private ByteArrayPusher bytearrayPusher = null;

	public UDPServer() {
		timestampLastUpdate = getCurrentTimestamp();
	}

	protected void finalize() {
		stop();
	}

	public void start(JLabel labelForStats) {
		active = true;
		this.labelForStats = labelForStats;
		numberFormatString = "######0.##";
		numberFormat = new DecimalFormat(numberFormatString,
				new DecimalFormatSymbols(Locale.US));

		nbPushesSinceStart = 0;
		resetStats();
		timestampLastUpdate = getCurrentTimestamp();

		if (bytearrayPusher != null) {
			bytearrayPusher.clear();
			bytearrayPusher.cancel();
		}
		bytearrayPusher = new ByteArrayPusher();

		bytearrayPusherTimer = new Timer();
		bytearrayPusherTimer.schedule(bytearrayPusher, 0, 100);

		try {
			socket = new DatagramSocket(PORT);
		} catch (SocketException e) {
			System.err.println(e);
			return;
		}

		serverThread = new Thread(this);
		serverThread.start();
	}

	@SuppressWarnings("deprecation")
	public void stop() {
		active = false;

		if (socket != null)
			socket.close();
		if (serverThread != null)
			serverThread.stop();

		if (bytearrayPusherTimer != null)
			bytearrayPusherTimer.cancel();

		if (bytearrayPusher != null)
			bytearrayPusher.clear();
	}

	public void run() {
		if (socket == null)
			return;

		resetStats();
		timestampLastUpdate = getCurrentTimestamp();

		DatagramPacket packet = new DatagramPacket(new byte[BUFFERSIZE],
				BUFFERSIZE);

		while (active) {
			try {
				socket.receive(packet);
				byte[] arr = packet.getData();
				int packetLength = packet.getLength();

				ByteBuffer buf = ByteBuffer.wrap(arr);
				byte[] arrToPush = new byte[packetLength];
				buf.get(arrToPush);
				updateStats(false, packetLength);
				bytearrayPusher.addByteArray(arrToPush);
			} catch (IOException ioe) {
				System.out.println(ioe);
			}
		}
	}

	// if output == false -> value == packetLength
	// if output == true -> value == updateInterval
	public synchronized void updateStats(boolean output, int value) {
		if (!output) {
			nbPushesSinceLastUpdate++;
			totalNbBytes += value;
			nbPushesSinceStart++;
		} else {
			Timestamp now = getCurrentTimestamp();
			if (now.getTime() - value >= timestampLastUpdate.getTime()) {
				long diff = now.getTime() - timestampLastUpdate.getTime();

				double avgUpSpeed = (1.0 * totalNbBytes / 1000.0)
						/ (diff / 1000); // kB/s
				double avgPacketSize = (1.0 * totalNbBytes)
						/ nbPushesSinceLastUpdate; // bytes
				double avgNbPushed = (1.0 * nbPushesSinceLastUpdate)
						/ (diff / 1000);

				String text = "<html>&nbsp;:: Server Statistics ::<br>&nbsp;&nbsp;&nbsp;&nbsp;  Upload rate: "
						+ numberFormat.format(avgUpSpeed)
						+ " kB/s"
						+ "<br>&nbsp;&nbsp;&nbsp;&nbsp;  Packet rate: "
						+ ((long) avgNbPushed)
						+ " packets/s"
						+ "<br>&nbsp;&nbsp;&nbsp;&nbsp;  Average packet size: "
						+ ((long) avgPacketSize)
						+ " bytes"
						+ "<br>&nbsp;&nbsp;&nbsp;&nbsp;  #UDP packets received: "
						+ nbPushesSinceStart + "</html>";
				labelForStats.setText(text);

				resetStats();
				timestampLastUpdate = getCurrentTimestamp();
			}
		}
	}

	private synchronized void resetStats() {
		nbPushesSinceLastUpdate = 0;
		totalNbBytes = 0;
		timestampLastUpdate = null;
	}

	private Timestamp getCurrentTimestamp() {
		Calendar calendar = Calendar.getInstance();
		java.util.Date now = calendar.getTime();
		return new java.sql.Timestamp(now.getTime());
	}
}
